
// ===============================================================================================================
// -*- C++ -*-
//
// Player.cpp - Player class.
//
// Copyright (c) 2011 Guilherme R. Lampert
// guilherme.ronaldo.lampert@gmail.com
//
// This code is licenced under the MIT license.
//
// This software is provided "as is" without express or implied
// warranties. You may freely copy and compile this source into
// applications you distribute provided that the copyright text
// above is included in the resulting source code.
//
// ===============================================================================================================

#include <Player.hpp>
#include <Renderer.hpp>
#include <Matrix4x4.hpp>

// =========================================================
// Player Class Implementation
// =========================================================

// Amount to move camera via keyboard and mouse input.
const float Player::CAM_MOVE_SPEED = 50.0f * (1.0f / 60.0f);
const float Player::CAM_ROTATE_SPEED = 5.0f * (1.0f / 60.0f);

Player::Player(int intialAmmo, const Vec3 & initialPos) : camera(), pitchAmt(0.0f), rAngle(0.0f), weapon(0), hudTextArea(0)
{
	try {

		weapon = new DoublebarrelShotgun(intialAmmo);
	}
	catch (std::exception & err) { // Throws std::runtime_error on failure.

		LOG_ERROR(err.what());
	}

	// Hardcoded for now...
	hudTextArea = ImageFactory::CreateImageFromFile("Assets/Sprites/HudTextArea.tga");

	camera.SetEye(initialPos);
}

Player::~Player(void)
{
	// Release the references / free memory:

	if (weapon)
	{
		delete weapon;
	}

	if (hudTextArea)
	{
		hudTextArea->Release();
	}
}

void Player::Update(const GameLevel * level, float elapsedTime)
{
	// Check user input:
	CameraKeyboardInput();
	CameraMouseInput();

	// Set the current camera:
	Renderer::Instance()->SetCamera(&camera);

	// Do bounds checking to avoid leaving the level area:
	Vec3 eye(camera.GetEye());

	const float mapScale = level->GetScale();

	int max_x, max_z;
	level->GetExtents(max_x, max_z);

	if (eye.x < mapScale)
	{
		eye.x = mapScale;
	}

	if (eye.x > (max_x - 2) * mapScale)
	{
		eye.x = (max_x - 2) * mapScale;
	}

	if (eye.z < -(max_z - 2) * mapScale)
	{
		eye.z = -(max_z - 2) * mapScale;
	}

	if (eye.z > -mapScale)
	{
		eye.z = -mapScale;
	}

	// Update the camera Y based on the ground level:
	eye.y = level->GetHeightAt(eye.x, eye.z) + 17.0f; // + 17.0f is just a manual adjust...
	camera.SetEye(eye);

	weapon->Update(elapsedTime);
}

void Player::DisplayHUDAndWeapon(void)
{
	// Weapon Drawing //

	// Constant matrix made especially for the Doublebarrel Shotgun:
	static const Matrix4x4 weaponOffset(
	-0.036089f, -0.247381f, 0.0f,       0.6f,
	 0.028004f, -0.004085f, 0.248393f, -1.0f,
	-0.245791f,  0.035857f, 0.028301f, -3.2f,
	 0.0f,       0.0f,      0.0f,       1.0f);

	Renderer * theRenderer = Renderer::Instance();
	theRenderer->DrawPlayerWeapon(weapon->GetModel(), weaponOffset.m);

	// HUD Drawing //

	const int y0 = Renderer::videoResolutionX - (hudTextArea->Width() << 1);
	const int y1 = Renderer::videoResolutionX - hudTextArea->Width();

	theRenderer->PrintString(y0 - 40, (Renderer::videoResolutionY - 10), PackRGBA(50, 128, 50, 255), "Clips: %d", weapon->NumClips());
	theRenderer->DrawImage(y0 - 50, Renderer::videoResolutionY, hudTextArea);

	theRenderer->PrintString(y1 + 10, (Renderer::videoResolutionY - 10), PackRGBA(50, 128, 50, 255), "Shells: %d", weapon->BulletsInCurrClip());
	theRenderer->DrawImage(y1, Renderer::videoResolutionY, hudTextArea);

	theRenderer->DrawImage(0, Renderer::videoResolutionY, hudTextArea);
}

bool Player::Fail(void) const
{
	return ((weapon == 0) || (hudTextArea == 0));
}

void Player::CameraKeyboardInput(void)
{
	// Standard FPS Controls //

	if (KeyDown('W') || KeyDown(VK_UP))
	{
		camera.Move(Camera::FORWARD, CAM_MOVE_SPEED, 1.0f, 0.0f, 1.0f);
	}
	if (KeyDown('S') || KeyDown(VK_DOWN))
	{
		camera.Move(Camera::BACK, CAM_MOVE_SPEED, 1.0f, 0.0f, 1.0f);
	}
	if (KeyDown('D') || KeyDown(VK_RIGHT))
	{
		camera.Move(Camera::RIGHT, CAM_MOVE_SPEED, 1.0f, 0.0f, 1.0f);
	}
	if (KeyDown('A') || KeyDown(VK_LEFT))
	{
		camera.Move(Camera::LEFT, CAM_MOVE_SPEED, 1.0f, 0.0f, 1.0f);
	}
}

void Player::CameraMouseInput(void)
{
	// NOTE: Windows dependant!

	static const float maxAngle = 89.5f; // Max degrees of rotation

	// Get the middle of the screen
	int midScrX = GetSystemMetrics(SM_CXSCREEN) >> 1;
	int midScrY = GetSystemMetrics(SM_CYSCREEN) >> 1;

	float amt;
	POINT pt = {0};
	GetCursorPos(&pt); // Get the current mouse position

	// Rotate left/right
	amt = static_cast<float>(midScrX - pt.x) * CAM_ROTATE_SPEED;
	rAngle = amt * Math::DEG_TO_RAD; // Store angle in radians
	camera.Rotate(rAngle);

	// Calculate amount to rotate up/down
	amt = static_cast<float>(midScrY - pt.y) * CAM_ROTATE_SPEED;

	// Clamp pitch amount
	if ((pitchAmt + amt) <= -maxAngle)
	{
		amt = -maxAngle - pitchAmt;
		pitchAmt = -maxAngle;
	}
	else if ((pitchAmt + amt) >= maxAngle)
	{
		amt = maxAngle - pitchAmt;
		pitchAmt = maxAngle;
	}
	else
	{
		pitchAmt += amt;
	}

	// Pitch camera
	camera.Pitch(amt * Math::DEG_TO_RAD);	

	// Set our cursor back to the middle of the screen to avoid problems
	SetCursorPos(midScrX, midScrY);
}